﻿using System;
using System.Collections.Generic;

namespace SingleBlog.Core.Services
{
    using Data;
    using Microsoft.EntityFrameworkCore;
    using System.Linq;
    using System.Threading.Tasks;
    using Resources;
    using System.Linq.Expressions;

    /// <summary>
    /// 表示文章的服务。
    /// </summary>
    public class ArticleService
    {
        public class Query
        {
            public int Page { get; set; } = 1;
            public int Size { get; set; } = 10;
            public int? CategoryId { get; set; }

            public OrderBy OrderBy { get; set; } = OrderBy.CreateTimeDesc;

            public Expression<Func<Article, bool>> Eval { get; set; }
        }

        public enum OrderBy
        {
            CreateTimeDesc = 0,
            ViewCountDesc = 1,
        }
        
        private readonly SingleBlogDbContext _context;

        /// <summary>
        /// 初始化 <see cref="ArticleService"/> 类的新实例。
        /// </summary>
        /// <param name="cache">缓存注入。</param>
        /// <param name="context"><see cref="SingleBlogDbContext"/> 注入。</param>
        public ArticleService(SingleBlogDbContext context)
        {
            _context = context;
        }

        /// <summary>
        /// 以异步方式获取所有的文章。
        /// </summary>
        /// <param name="take">获取的数据量。</param>
        /// <param name="skip">跳过的数据量。</param>
        /// <param name="categoyrId">分类Id。</param>
        /// <param name="onlyRecommand">是否只获取推荐的数据。</param>
        /// <returns></returns>
        public Task<IEnumerable<Article>> GetArticlesAsync(int take, int skip = 0, int? categoyrId = default(int?), bool onlyRecommand = false)
            => Task.Run(() =>
            {
                IQueryable<Article> queryable = _context.Articles
                 .Include(m => m.Category)
                 .Include(m => m.User)
                    .Include(m => m.Comments)
                 .AsNoTracking()
                .Skip(skip)
                .Take(take);

                if (categoyrId.HasValue)
                {
                    queryable = queryable.Where(m => m.CategoryId == categoyrId.Value);
                }
                if (onlyRecommand)
                {
                    queryable = queryable.Where(m => m.IsRecommand);
                }


                return queryable.OrderBy(m => m.DisplayOrder)
                .ThenByDescending(m => m.CreateTime).ToList().AsEnumerable();
            });

        /// <summary>
        /// 以异步的方式获取文章的分页集合。
        /// </summary>
        /// <param name="page">当前页码。</param>
        /// <param name="size">每一页所需的数据量。</param>
        /// <returns></returns>
        public Task<PagedCollection<Article>> GetArticlesAsync(Query query = default(Query))
        => Task.Run(() => {
            IQueryable<Article> queryable = _context.Articles;
            if (query.CategoryId.HasValue)
            {
                queryable = queryable.Where(m => m.CategoryId == query.CategoryId.Value);
            }

            if (query.Eval != null)
            {
                queryable = queryable.Where(query.Eval);
            }

            queryable = queryable
            .Include(m => m.Category)
            .Include(m => m.User)
                    .Include(m => m.Comments)
            .AsNoTracking();

            IOrderedQueryable<Article> orderedQueryable= queryable.OrderBy(m=>m.DisplayOrder);
            switch (query.OrderBy)
            {
                case OrderBy.ViewCountDesc:
                    orderedQueryable = orderedQueryable.ThenByDescending(m => m.ViewCount);
                    break;
                default:
                    orderedQueryable = orderedQueryable.ThenByDescending(m => m.CreateTime);
                    break;
            }
            return orderedQueryable.Paged(query.Page, query.Size).AsPageCollection();
        })
                ;

        /// <summary>
        /// 以异步方式获取指定文章Id的数据，优先从缓存中读取。
        /// </summary>
        /// <param name="id">要读取的文章Id。</param>
        /// <param name="addViewCount">是否增加阅读量。</param>
        /// <returns></returns>
        public async Task<Article> GetArticleAsync(int id, bool addViewCount = true)
        {
            var entity = await _context.Articles
                    .Include(m => m.User)
                    .Include(m => m.Comments)
                    .SingleOrDefaultAsync(m => m.Id == id);
            if (entity != null && addViewCount)
            {
                entity.IncreseView();
            }
            return entity;
        }

        /// <summary>
        /// 以异步的方式保存文章，若文章的Id==0，表示新增文章，否则为更新文章所有字段的值。
        /// </summary>
        /// <param name="entity">要保存的文章实体。</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException">entity 是 null。</exception>
        public async Task SaveArticleAsync(Article entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException(nameof(entity));
            }

            if (entity.Id == 0)
            {
                _context.Articles.Add(entity);
            }
            else
            {
                _context.Entry(entity).State = EntityState.Modified;
            }
             await _context.SaveChangesAsync();
        }

        /// <summary>
        /// 以异步的方式删除指定的文章。
        /// </summary>
        /// <param name="id">要删除的文章Id。</param>
        /// <returns></returns>
        /// <exception cref="NullReferenceException">指定的文章不存在。</exception>
        public async Task DeleteArticleAsync(int id)
        {
            var articleToDelete = await _context.Articles.FindAsync(id);
            if (articleToDelete == null)
            {
                throw new NullReferenceException(Language.ArticleNotFound.StringFormat(id));
            }

            _context.Articles.Remove(articleToDelete);
            await _context.SaveChangesAsync();
        }

        public Task<IEnumerable<Article>> GetRecommandArticlesAsync(int? categoryId = default(int?))
        => 
             Task.Run(()
                =>
            {
                IQueryable<Article> articles = _context.Articles
                    .Include(m => m.User)
                    .Include(m => m.Category)
                    .Include(m=>m.Comments)
                    .AsNoTracking()
                    .Where(m => m.IsRecommand);
                if (categoryId.HasValue)
                {
                    articles = articles.Where(m => m.CategoryId == categoryId.Value);
                }

                return articles.OrderBy(m => m.DisplayOrder)
                   .OrderByDescending(m => m.CreateTime)
                   .ToList()
                   .AsEnumerable();
            });

        /// <summary>
        /// 以异步方式设置指定文章成为推荐或取消推荐。
        /// </summary>
        /// <param name="id">要设置的文章Id。</param>
        /// <returns></returns>
        /// <exception cref="NullReferenceException"></exception>
        public async Task SetRecommandAsync(int id)
        {
            var article = await _context.Articles.FindAsync(id);
            if (article == null)
            {
                throw new NullReferenceException(Language.ArticleNotFound.StringFormat(id));
            }
            if (article.IsRecommand)
            {
                article.Unrecommand();
            }
            else
            {
                article.Recommand();
            }
            await _context.SaveChangesAsync();
        }
        
    }
}
